﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace 事件
{
    #region ObSever 设计模式
    /*
     * https://baike.baidu.com/item/c%23%E5%A7%94%E6%89%98
     * 
     * publish - subcribe （observer 设计模式）
     *  发布   -  订阅
     *  Observer设计模式是为了定义对象间的一种一对多的依赖关系，
     *  以便于当一个对象的状态改变时，其他依赖于它的对象会被自动告知并更新。
     *  Observer模式是一种松耦合的设计模式。
     *  
     *  多播委托是什么？
     *  定义是什么？
     *  作用是什么？
     *  和传统的委托的区别在哪里？
     *  优缺点?
     *  使用场景？
     *  是否可以使用其它方法代替？
     *  
     *  1.是一种通用的开发模式，
     *  2.这个模式成为 observer（观察者)或者 publish - subcribe  
     *  2.1可以把发布看作是一个具有放射性的类 而订阅者就是受到辐射的类 而订阅者和发布者链接的方式可以称之为委托链（依稀记得好像有一个概念叫继承连来着。。）
     *  3.可以避免大量的手工代码 (懒人的最爱)
     *  4.应对场景： 需要将单一事件的通知（如对象状态发生的一个变化）广播给多个订阅者  ps:有点像udp
     *  5.可以很方便的再多个方法内创建监听，并改变方法内的值 （脑袋里灵光一闪，吗的想不起来了了）
     *  
     */

    #region 订阅者

    class Cooler
    {
        public Cooler(float fa)
        {
            Fa = fa;
        }

        public float Fa { get; set; }
        //Cooler
        public void OnTC(float newfa)
        {
            if (newfa > Fa)
            {
                Console.WriteLine("Cooler:on");
            }
            else
            {
                Console.WriteLine("Cooler:off");
            }
        }
    }

    class Heat
    {
        public Heat(float fa)
        {
            Fa = fa;
        }

        public float Fa { get; set; }
        //Heat
        public void OnTC(float newfa)
        {
            if (newfa < Fa)
            {
                Console.WriteLine("Heat:on");
            }
            else
            {
                Console.WriteLine("Heat:off");
            }
        }
    }

    #endregion

    #region 发布者
    public class Thermostat
    {

        public Action<float> action { get; set; }

        public float CurrentT
        {
            get
            {
                return _CurrentT;
            }
            set
            {
                if (value != _CurrentT)
                {
                    _CurrentT = value;
                    /*将温度变化传播给 他的订阅者们    
                     * thermostat.action += heat.OnTC;    
                     * thermostat.action += cooler.OnTC;
                     * 
                     * 检测是否为null 值
                     * 假如订阅者没有注册接收通知，那么 action 就会为空 
                     * action(value); 指向后会报一个 NullReferenceException（尝试取消引用空对象引用时引发的异常。）
                     * 
                     * 
                     *  null 条件操作符   变量名?.属性  
                     *  
                     *  语法糖 等价于    string pName = p == null ? null : p.Name;
                     *  
                     */
                    //未检测 null
                    // action(value);
                    //检测 null
                    //方法一
                    action?.Invoke(value);
                    //方法二
                    //先给一个委托赋值，然后再比较是否为空
                    //Action<float> actions = action;
                    //if (actions !=null)
                    //{
                    //    action(value);
                    //}


                }
            }
        }
        private float _CurrentT;
    }


    #endregion

    #region 连接订阅者和发布者
    public class Connect
    {
        public static void Connect_main()
        {
            //声明对象
            Thermostat thermostat = new Thermostat();
            Cooler cooler = new Cooler(50);
            Heat heat = new Heat(100);
            string tea;
            ////创建监听 （创建订阅事件）
            //thermostat.action += heat.OnTC;
            //thermostat.action += cooler.OnTC;
            ////使用 -=会返回一个新的实例
            //thermostat.action -= cooler.OnTC;

            /*
              IL_0058:  castclass  class [System.Runtime]System.Action`1<float32>
              IL_005d:  callvirt   instance void '事件'.Thermostat::set_action(class [System.Runtime]System.Action`1<float32>)
             */

            //Console.WriteLine("esss");
            //tea = Console.ReadLine();
            //thermostat.CurrentT = int.Parse(tea);

            //使用 + - 委托操作符

            Action<float> action1;
            Action<float> action2;
            Action<float> action3;

            action1 = heat.OnTC;
            action2 = cooler.OnTC;
            Console.WriteLine("/***********/");
            //合并委托
            action3 = action1 + action2; //老一 
            action3(60);
            Console.WriteLine("/************/");
            //去除指定委托 action2
            action3 = action1 - action2;//note： 使用赋值操作符会清空之前所有的订阅者  老一：我变成null啦，快给我赋值吧
            action3(60);
            /*
             *  += 创建一个监听（订阅者）或者说再绑定一个方法
             *  -= 取消一个监听（订阅者）或者说取消委托对一个方法的绑定
             *  + 合并委托（订阅者） 或者说执行两个委托方法
             *  
             *  - 删除委托 取消指定的委托方法的调用
             *  
             *  system.delegate.combine() 将两个委托的调用列表连接在一起。
             *  
             *  system.delegate.remove()  从一个委托的调用列表中移除另一个委托的所有调用列表。
             *  
             *  无论是 + - 是 += -=   他梦的内部机制都是通过 静态方法 system.delegate.combine()  和 system.delegate.remove() 实现的
             *  
             *  有趣的是combine()  允许两参数都为 null ，如果只有一个为 null 就返回非空的那个，两个都为 null 就返回 null
             *  
             *  这就可以解释 当你调用某一个事件时，即使这个事件的指向为空他也不会报错。
             *  
             *  
             *  
             */
        }
    }
    #endregion

    #region 异常处理
    /*
     * 
     * observer 设计模式
     * 当一个发布者 存在多个订阅者的时候，当发布者发生改变时，订阅者也会相应地发生改变
     * 这个时候就会存在一种情况，就是发布者发布的信息对于某个订阅者来说是无效的，这时订阅者就会报出处理异常
     * 并且后续的订阅者就接收不到通知了（从这里可以看出，observer 设计模式时单线程的）
     * 
     * 如果想继续执行，处理来自订阅者的异常了
     */

    class ObsError

    {
        public Action<float> OnTC;
        public float CurrentTp
        {
            get { return _CurrentTp; }

            set
            {
                if (value != CurrentTp)
                {
                    _CurrentTp = value;
                    Action<float> onTC = OnTC;
                    if (onTC != null)
                    {
                        //创建异常列表
                        List<Exception> exceptions = new List<Exception>();
                        // GetInvocationList 按照调用顺序返回此多路广播委托的调用列表。
                        //遍历多播委托列表
                        foreach (Action<float> item in onTC.GetInvocationList())
                        {
                            try
                            {
                                item(value);
                            }
                            catch (Exception e)
                            {
                                //存在异常时 把异常添加到异常列表中
                                exceptions.Add(e);
                            }
                        }
                        //假如异常列表不为空
                        if (exceptions.Count > 0)
                        {
                            throw new AggregateException("订阅者存在异常", exceptions);
                        }
                    }
                }
            }
        }

        private float _CurrentTp;
    }


    #endregion

    #endregion

    #region 事件
    /*
     * 
     * 关键字 event
     * 
     * event 关键字用于在发行者类中声明事件。
     * 
     * 事件是特殊类型的多路广播委托，
     * 仅可从声明它们的类或结构（发行者类）中调用。 
     * 如果其他类或结构订阅了该事件，则当发行者类引发该事件时，会调用其事件处理程序方法。 
     * 
     * 
     * 事件和委托的异同
     * 
     * 1.事件是特殊类型的委托，事件是委托，但委托不一定是事件
     * 2.事件的作用域实在其包容类内，而委托可以作用于任何类存在的地方
     * 3.事件的本质就是解决我们定义委托是由于书写错误所引发的 BUG
     * 4.事件的访问性默认情况下都是 private 的
     * 5.事件限制赋值只能在类的内部发生，
     * 6.事件会检查委托是否非空，而普通委托只能自己检测
     * 7.雾草，想不出来了
     * 
     * 作用：
     * 当我们使用委托时，一般都是用 += 操作符，但是有时候由于习惯原因很可能就只剩下了 
     * = 当用的时 = 的时候 他就会从新创造一个委托链，也就是说先前的委托链被清空了
     * 这样一来 后续的订阅者也就被一一取消了
     * 难道这就没办法了吗？
     * 当然是有的
     * 最好的解决方法就是 只为包容类内部的对象提供对赋值操作符的支持（也就是说，当前委托只作用于当前包容类）
     * 
     * 而事件就提供了 这种额外的封装，避免不小心取消其他订阅者
     * 
     * 事件规范：
     * 1.要再调用委托前检查他的只不为空（在 C# 6.0 可以用 null 条件操作符 ?）
     * 2.不要为非静态事件的 sender 传递 null
     * 3.要为静态事件的 sender 传递 null 值
     * 4.不要为 eventArgs 参数传递null 值
     * EventHandler 表示将处理不包含事件数据的事件的方法。
     * EventHandler 委托是专门表示事件的事件处理程序方法不生成数据的预定义的委托
     * 。 如果您的事件生成数据，必须使用泛型 EventHandler<TEventArgs> 委托选件类。
     * 5.要为事件按使用 EventHandler<TEvenArgs> 委托类型
     * 6.要为 TEvenArgs 使用 system.eventargs 类型或者他的派生类型
     * 7.考虑使用 system.eventargs 的子类作为事件的实参类型 TEvenArgs ，除非完全确定事件永远不需要携带任何数据。
     * 
     */
    #endregion

    #region CLR via C#


    #region 公开事件类型




    public delegate void TestEventArgs<T>(object sender, T e);

    class NewTestEventArgs : EventArgs
    {
        private readonly string _msg;
        public NewTestEventArgs(string msg)
        {
            _msg = msg;
        }

        public string Msg { get { return _msg; } }
    }
    class MassageTest
    {
        //public delegate string EventTest<T>(Object obj, NewTestEventArgs e);

        public event TestEventArgs<NewTestEventArgs> Events;

        /// <summary>
        /// 版本1
        /// 非线程安全 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void onNewMsg(NewTestEventArgs e)
        {
            /*
             * 虽然可以检测  Events 不为null, 但在调用  Events 之前
             * 另一个线程可能从委托链中移除了一个委托，使 Events 变为 null
             * 这样会报对象为空的异常
             */
            if (Events != null)
            {
                Events(this, e);
            }
        }

        /// <summary>
        /// 版本2
        /// 线程安全，但编译器可能会优化从而又导致 空异常。
        /// </summary>
        /// <param name="e"></param>
        protected virtual void onNewMsg1(NewTestEventArgs e)
        {
            /*
             * 先将  Events 的应用复制到临时变量 eventTest 上，
             * 然后比较 临时变量  eventTest ，后调用  eventTest 
             * 这样即使另一个线程改变了 Events 也没关系，委托是不可变的，
             * 这个方法理论上行得通，但编译器可能会通过 完全一处局部变量  eventTest
             * 的方式对方法代码进行优化，如果发送了这种情况，那么 版本1和版本2 就没有任何区别了
             */
            TestEventArgs<NewTestEventArgs> eventTest = Events;
            if (eventTest != null)
            {
                eventTest(this, e);
            }
        }

        /// <summary>
        /// 版本3
        /// 线程安全，完美的解决思路
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="e"></param>
        protected virtual void onNewMsg(Object obj, NewTestEventArgs e)
        {
            /*
             * Volatile.Read(ref Events) 的使用 强迫 Events 在这个调用发生的时候读取，
             * 引用为真的，就给 eventTest 赋值
             * 然后 eventTest 就只会在不为空的时候才会被调用。
             * 
             */
            TestEventArgs<NewTestEventArgs> eventTest = Volatile.Read(ref Events);
            if (eventTest != null)
            {
                eventTest(this, e);
            }
        }

        //protected virtual void onNewMsgw(NewTestEventArgs e)
        //{
        //    e.Raise(this, ref new m_NewMail);
        //}

        public void SayMsg(string name)
        {
            //构造对象存放传给通知接收者的信息
            NewTestEventArgs e = new NewTestEventArgs(name);
            //通知虚方法 事件已经发生
            //如果没有类型重写该方法，我们的对象将通知时间的所有登记对象
            onNewMsg(null, e);
        }
    }

    public static class EventArgExtensions
    {
        public static void Raise<T>(this T t, Object sender, ref EventHandler<T> eventDelegate)
        {
            EventHandler<T> temp = Volatile.Read(ref eventDelegate);
            if (temp != null)
            {
                temp(sender, t);
            }
        }
    }

    #endregion

    #region 监听事件
    class Fax
    {
        public Fax(MassageTest massage)
        {
            //注册
            massage.Events += Massage_Events;

        }

        private void Massage_Events(object sender, NewTestEventArgs e)
        {
            Console.WriteLine(e.Msg);
        }

        public void Unregister(MassageTest massage)
        {
            //注销
            massage.Events -= Massage_Events;
        }
    }

    class Fax1
    {
        public Fax1(MassageTest massage)
        {
            //注册
            massage.Events += Massage_Events;

        }

        private void Massage_Events(object sender, NewTestEventArgs e)
        {
            Console.WriteLine(e.Msg + "rrrrr");
        }

        public void Unregister(MassageTest massage)
        {
            //注销
            massage.Events -= Massage_Events;
        }
    }


    #endregion

    #endregion


    #region CLR via C#
    public sealed class EventKey { }

    public sealed class EventSet
    {
        private readonly Dictionary<EventKey, Delegate> m_events = new Dictionary<EventKey, Delegate>();

        public void Add(EventKey key, Delegate handler)
        {
            Monitor.Enter(m_events);
            Delegate d;
            m_events.TryGetValue(key, out d);//相当与创建一个建值
            m_events[key] = Delegate.Combine(d, handler);//将委托添加到委托链表中
            Monitor.Exit(m_events);
        }

        public void Remove(EventKey key, Delegate handler)
        {
            //同步机制锁
            Monitor.Enter(m_events);
            Delegate d;
            if (m_events.TryGetValue(key, out d))
            {
                d = Delegate.Remove(d, handler);
                if (d != null)
                {
                    m_events[key] = d;
                }
                else
                {
                    m_events.Remove(key);
                }
            }
            Monitor.Exit(m_events);
        }

        public void Raise(EventKey key, object sender, EventArgs e)
        {
            Delegate d;
            Monitor.Enter(m_events);
            m_events.TryGetValue(key, out d);
            Monitor.Exit(m_events);
            if (d != null)
            {
                /*
                 * 由于字典可能包含几个不同的委托类型，所有 无法在编译时构造一个类型安全的委托调用
                 * 使用 DynamicInvoke 方法 以一个对象数值的形式向它传递回调方法的参数
                 * 在内部， DynamicInvoke 会向调用的回调方法查证参数的类型安全性，
                 * 如果类型不匹配，DynamicInvoke 会抛出异常
                 */
                d.DynamicInvoke(new Object[] { sender, e });
            }
        }


    }
    public class FooEventArgs : EventArgs { }

    public class TypeWithLotsOfEvents
    {   //事件集合
        private readonly EventSet eventSet = new EventSet();

        protected EventSet EventSet { get { return eventSet; } }
        //每个对象都有自己的哈希码，以便在对象的集合中查找这个事件的委托链表
        protected static readonly EventKey eventKey = new EventKey();

        /// <summary>
        /// 事件访问器，用于集合中增删委托
        /// </summary>
        public event EventHandler<FooEventArgs> Foo
        {
            add { eventSet.Add(eventKey, value); }
            remove { eventSet.Remove(eventKey, value); }
        }
        /// <summary>
        /// 为事件定义受保护的虚方法
        /// </summary>
        /// <param name="e"></param>
        protected virtual void onFoo(FooEventArgs e)
        {
            eventSet.Raise(eventKey, this, e);
        }
        public void SimulateFoo() { onFoo(new FooEventArgs()); }


    }
    #endregion

    #region 启动类
    class Program
    {
        static void Main(string[] args)
        {
            TypeWithLotsOfEvents typeWith = new TypeWithLotsOfEvents();
            typeWith.Foo += TypeWith_Foo;
            typeWith.SimulateFoo();
            //Connect.Connect_main();
            //obsError();
            //Console.ReadLine();


            //MassageTest massageTest = new MassageTest();
            //Fax fax = new Fax(massageTest);
            //Fax1 fax1 = new Fax1(massageTest);

            //massageTest.SayMsg("大侠，请留步");
            //massageTest.SayMsg("223344");

        }

        private static void TypeWith_Foo(object sender, FooEventArgs e)
        {
            Console.WriteLine("大侠，请留步");
        }

        /// <summary>
        /// 异常处理
        /// </summary>
        public static void obsError()
        {
            ObsError obsError = new ObsError();
            Cooler cooler = new Cooler(50);
            Heat heat = new Heat(100);

            obsError.OnTC += heat.OnTC;
            obsError.OnTC += cooler.OnTC;
            string tea;

            Console.WriteLine("esss");
            tea = Console.ReadLine();
            obsError.CurrentTp = int.Parse(tea);

        }
    }
    #endregion








































    /******************************************************/
}
